﻿using System;
using System.Collections.Generic;
using System.Data.SqlClient;
using System.Linq;
using System.Text;
using NUnit.Framework;
using PropertyExpression.Common;
using PropertyExpression.ControllableQuery.Test.Queries;
using PropertyExpression.ControllableQuery.Test.Queries.StringBuilding;
using PropertyExpression.ControllableQuery.Test.Queries.T4;
using PropertyExpression.ControllableQuery.TypedReader;
using ModelPublicationSearchQuery = PropertyExpression.ControllableQuery.Test.Queries.T4.ModelPublicationSearchQuery;

namespace PropertyExpression.ControllableQuery.Test
{
    public class PublicationTypeUpdateInfo
    {
        private readonly Guid publicationTypeId;
        private readonly int code;

        public PublicationTypeUpdateInfo(Guid publicationTypeId, int code)
        {
            this.publicationTypeId = publicationTypeId;
            this.code = code;
        }

        public Guid PublicationTypeId
        {
            get { return publicationTypeId; }
        }

        public int Code
        {
            get { return code; }
        }
    }

    [TestFixture]
    public class MainTest
    {
        [Test]
        public void Test1()
        {
            var publicationTypeUpdateInfo = new PublicationTypeUpdateInfo(Guid.NewGuid(), 1);
            ExecuteNonQuery(
                queryContext =>
                new QueryImpl(
                    () =>
                        {
                            var columnStringBuilder = new StringBuilder();
                            var valueStringBuilder = new StringBuilder();
                            foreach (var propertyInfo in publicationTypeUpdateInfo.GetType().GetProperties())
                            {
                                if (columnStringBuilder.Length == 0) columnStringBuilder.Append(",");
                                columnStringBuilder.Append(propertyInfo.Name);

                                if (valueStringBuilder.Length == 0) valueStringBuilder.Append(",");
                                propertyInfo.GetValue(publicationTypeUpdateInfo, null);
                                valueStringBuilder.Append()
                            }

                            queryContext.CreateParam(Guid.NewGuid()).ToSql("@PublicationTypeId");
                            queryContext.CreateParam(1).ToSql("@Code");
                            return
@"INSERT INTO
    PublicationType
    (
     " + columnStringBuilder + @" 
    )
VALUES
    (@PublicationTypeId,@Code
     )";
                        }
                    )
                );
        }

        private class QueryImpl: IQuery
        {
            private readonly Func<string> func;

            public QueryImpl(Func<string> func)
            {
                this.func = func;
            }

            public string TransformText()
            {
                return func();
            }
        }

        [Test]
        public void PartLinkQuery()
        {
            Console.WriteLine("Test1");
            WriteQueryResult(
                ExecuteReader(
                    queryContext => new PartLinkQuery(
                                        queryContext.CreateParam(Guid.NewGuid()),
                                        queryContext.CreateParam(Guid.NewGuid()),
                                        queryContext.CreateParam(Guid.NewGuid()),
                                        new WithoutInnerPartLinkSwitcher()
                                        )
                    )
                );
            Console.WriteLine("Test2");
            WriteQueryResult(
                ExecuteReader(
                    queryContext => new PartLinkQuery(
                                        queryContext.CreateParam(Guid.NewGuid()),
                                        queryContext.CreateParam(Guid.NewGuid()),
                                        queryContext.CreateParam(Guid.NewGuid()),
                                        new WithInnerPartLinkSwitcher(
                                            false,
                                            queryContext.CreateParam("en"),
                                            queryContext.CreateParam(Guid.NewGuid()))
                                        )
                    )
                );
            Console.WriteLine("Test3");
            WriteQueryResult(
                ExecuteReader(
                    queryContext => new PartLinkQuery(
                                        queryContext.CreateParam(Guid.NewGuid()),
                                        queryContext.CreateParam(Guid.NewGuid()),
                                        queryContext.CreateParam(Guid.NewGuid()),
                                        new WithInnerPartLinkSwitcher(
                                            true,
                                            queryContext.CreateParam("en"),
                                            queryContext.CreateParam(Guid.NewGuid()))
                                        )
                    )
                );
        }

        [Test]
        public void ModelPublicationSearchQuery()
        {
            Console.WriteLine("Test1");
            WriteQueryResult(
                ExecuteReader(
                    queryContext => new ModelPublicationSearchQuery(
                                        queryContext.CreateParam("PublicationCode1").AsOptionalValue(),
                                        OptionalValue.Nothing<IParam<string>>(),
                                        OptionalValue.Nothing<IParam<string>>()
                                        )
                    )
                );
            Console.WriteLine("Test2");
            WriteQueryResult(
                ExecuteReader(
                    queryContext => new ModelPublicationSearchQuery(
                                        queryContext.CreateParam("PublicationCode1").AsOptionalValue(),
                                        queryContext.CreateParam("ModelCode1").AsOptionalValue(),
                                        queryContext.CreateParam("ModelSequence1").AsOptionalValue()
                                        )
                    )
                );
            Console.WriteLine("Test3");
            WriteQueryResult(
                ExecuteReader(
                    queryContext => new ModelPublicationSearchQuery(
                                        OptionalValue.Nothing<IParam<string>>(),
                                        OptionalValue.Nothing<IParam<string>>(),
                                        OptionalValue.Nothing<IParam<string>>()
                                        )
                    )
                );
        }

        [Test]
        public void TableValuedParameterQuery()
        {
            WriteQueryResult(
                ExecuteReader(
                    queryContext =>
                    new TableValuedParameterQuery(
                        queryContext.CreateParam(
                            new IPublicationCodeType[]
                                {
                                    new PublicationCodeTypeImpl("PublicationCode1"),
                                    new PublicationCodeTypeImpl("PublicationCode2")
                                }
                            )
                        )
                    )
                );
        }

        [Test]
        public void StringBuildingModelPublicationSearchQuery()
        {
            Console.WriteLine("Test1");
            WriteQueryResult(
                ExecuteReader(
                    queryContext => new Queries.StringBuilding.ModelPublicationSearchQuery(
                                        queryContext.CreateParam("PublicationCode1").AsOptionalValue(),
                                        OptionalValue.Nothing<IParam<string>>(),
                                        OptionalValue.Nothing<IParam<string>>()
                                        )
                    )
                );
            Console.WriteLine("Test2");
            WriteQueryResult(
                ExecuteReader(
                    queryContext => new Queries.StringBuilding.ModelPublicationSearchQuery(
                                        queryContext.CreateParam("PublicationCode1").AsOptionalValue(),
                                        queryContext.CreateParam("ModelCode1").AsOptionalValue(),
                                        queryContext.CreateParam("ModelSequence1").AsOptionalValue()
                                        )
                    )
                );
            Console.WriteLine("Test3");
            WriteQueryResult(
                ExecuteReader(
                    queryContext => new Queries.StringBuilding.ModelPublicationSearchQuery(
                                        OptionalValue.Nothing<IParam<string>>(),
                                        OptionalValue.Nothing<IParam<string>>(),
                                        OptionalValue.Nothing<IParam<string>>()
                                        )
                    )
                );
        }

        [Test]
        public void StringBuildingModelPublicationQuery()
        {
            WriteQueryResult(
                ExecuteReader(
                    queryContext => new ModelPublicationQuery(
                                        queryContext.CreateParam("ModelCode1"),
                                        queryContext.CreateParam("ModelSequence1")
                                        )
                    )
                );
        }

        [Test]
        public void StringBuildingModelPublicationQuery2()
        {
            WriteQueryResult(
                ExecuteReader(
                    queryContext => new ModelPublicationQuery2(
                                        queryContext.CreateParam("ModelCode1"),
                                        queryContext.CreateParam("ModelSequence1")
                                        )
                    )
                );
        }

        [Test]
        public void PublicationInsertQuery()
        {
            var guid = Guid.NewGuid();
            ExecuteNonQuery(
                queryContext => new PublicationInsertQuery(
                                    queryContext.CreateParam(guid),
                                    queryContext.CreateParam(1)
                                    )
                );

            ExecuteNonQuery(
                queryContext => new PublicationDeleteQuery(
                                    queryContext.CreateParam(guid)
                                    )
                );
        }

        [Test]
        public void ModelPublicationQuery()
        {
            WriteQueryResult(
                ExecuteReader(
                    queryContext => Query.ModelPublicationQuery(
                        queryContext.CreateParam("ModelCode1"),
                        queryContext.CreateParam("ModelSequence1"))
                    )
                );
        }

        private static void WriteQueryResult(IEnumerable<IDataReader<IPartLinkInfo>> queryEnumerable)
        {
            foreach (var dataReader in queryEnumerable)
            {
                Console.WriteLine(
                    "PartNumber = '{0}'",
                    dataReader.GetValue(_ => _.PartNumber)
                    );
            }
        }

        private static void WriteQueryResult(IEnumerable<IDataReader<IModelPublicationInfo>> queryEnumerable)
        {
            foreach (var dataReader in queryEnumerable)
            {
                Console.WriteLine(
                    "PublicationId = '{0}', ModelCode = '{1}', ModelSequence = '{2}'",
                    dataReader.GetValue(_ => _.PublicationId),
                    dataReader.GetValue(_ => _.ModelCode),
                    dataReader.GetValue(_ => _.ModelSequence)
                    );
            }
        }

        private static void WriteQueryResult(IEnumerable<IDataReader<IPublicationInfo>> queryEnumerable)
        {
            foreach (var dataReader in queryEnumerable)
            {
                Console.WriteLine(
                    "PublicationId = '{0}'",
                    dataReader.GetValue(_ => _.PublicationId)
                    );
            }
        }

        public static IEnumerable<IDataReader<T>> ExecuteReader<T>(Func<QueryContext, IQuery<T>> queryFunc)
        {
            return WithSqlCommand(queryFunc, ExecuteReader);
        }

        public static int ExecuteNonQuery(Func<QueryContext, IQuery> queryFunc)
        {
            return WithSqlCommand(queryFunc, (query, sqlCommand) => new[] {sqlCommand.ExecuteNonQuery()}).Single();
        }

        private static IEnumerable<IDataReader<T>> ExecuteReader<T>(IQuery<T> query, SqlCommand sqlCommand)
        {
            using (var sqlDataReader = sqlCommand.ExecuteReader())
            {
                while (sqlDataReader.Read())
                {
                    yield return query.GetReader(sqlDataReader);
                }
            }
        }

        private static IEnumerable<TResult> WithSqlCommand<TQuery, TResult>(
            Func<QueryContext, TQuery> queryFunc, Func<TQuery, SqlCommand, IEnumerable<TResult>> func) where TQuery : IQuery
        {
            using (var connection = new SqlConnection(ConnectionString))
            {
                connection.Open();

                using (var sqlCommand = new SqlCommand())
                {
                    sqlCommand.Connection = connection;

                    var query = queryFunc(new QueryContext(sqlCommand));

                    sqlCommand.CommandText = query.TransformText();

                    foreach (var result in func(query, sqlCommand)) yield return result;
                }
            }
        }

        public static string ConnectionString
        {
            get { return @"Data Source=(local);Initial Catalog=ControllableQuery;Integrated Security=True"; }
        }

        private class PublicationCodeTypeImpl : IPublicationCodeType
        {
            private readonly string publicationCode;

            public PublicationCodeTypeImpl(string publicationCode)
            {
                this.publicationCode = publicationCode;
            }

            public string PublicationCode
            {
                get { return publicationCode; }
            }
        }
    }
}